#include <spdlog/async.h>                  //support for async logging.
#include <spdlog/fmt/ostr.h>               // must be included
#include <spdlog/sinks/basic_file_sink.h>  // support for basic file logging
#include <spdlog/sinks/daily_file_sink.h>
#include <spdlog/sinks/rotating_file_sink.h>  // support for rotating file logging
#include <spdlog/sinks/stdout_color_sinks.h>  // or "../stdout_sinks.h" if no colors needed
#include <spdlog/sinks/stdout_sinks.h>

#include <iterator>

//#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG  // define before including "spdlog.h", compile time set log level
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE
#include <spdlog/spdlog.h>

#include <iostream>

static std::string _myLogDir{MY_LOG_DIR};
static std::string _my_logger_te_2a{_myLogDir + "/te_2a.txt"};
static std::string _my_logger_te2b{_myLogDir + "/te_2b.txt"};
static std::string _my_logger_te_3{_myLogDir + "/te_3.txt"};
static std::string _my_logger_te_4{_myLogDir + "/te_4.txt"};
static std::string _my_logger_te_5{_myLogDir + "/te_5.txt"};
static std::string _my_logger_te_6{_myLogDir + "/te_6.txt"};

//===================== Use the default logger (stdout, multi-threaded, colored)
void te_1() { spdlog::info("Hello, {}!", "World"); }

//===================== basic logger
void te_2()
{
    try
    {
        // Create basic file logger (not rotated)
        auto my_logger = spdlog::basic_logger_mt("basic_logger", _my_logger_te_2a);
        my_logger->set_level(spdlog::level::debug);
        my_logger->info("my_logger info");
        my_logger->debug("my_logger debug");

        // create a file rotating logger with 5mb size max and 3 rotated files
        auto file_logger = spdlog::rotating_logger_mt("file_logger", _my_logger_te2b, 1024 * 1024 * 5, 3);
        file_logger->set_level(spdlog::level::trace);
        file_logger->info("file_logger info");
        file_logger->debug("file_logger debug");

        //--------------------- run time,  spdlog::set_level(spdlog::level::trace);
        // Notice that spdlog::set_level is also necessary to print out debug or trace messages.
        spdlog::set_level(spdlog::level::debug);
        SPDLOG_LOGGER_TRACE(file_logger, "Some trace message that will not be evaluated.{} ,{}", 1, 3.23);
        SPDLOG_LOGGER_DEBUG(file_logger, "Some Debug message that will be evaluated.. {} ,{}", 1, 3.23);
        SPDLOG_DEBUG("Some debug message to default logger that will be evaluated");
    }
    catch (const spdlog::spdlog_ex& ex)
    {
        std::cout << "Log initialization failed: " << ex.what() << std::endl;
    }
}

//===================== Create an asynchronous logger using factory method
void te_3()
{
    try
    {
        auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", _my_logger_te_3);
        for (int i = 1; i < 10; ++i)
        {
            async_file->info("Async message #{}", i);
        }

        // Under VisualStudio, this must be called before main finishes to workaround a known VS issue
        spdlog::drop_all();
    }
    catch (const spdlog::spdlog_ex& ex)
    {
        std::cout << "Log initialization failed: " << ex.what() << std::endl;
    }
}

//===================== Create an asynchronous logger and change thread pool settings
void te_4()
{
    try
    {
        auto daily_sink = std::make_shared<spdlog::sinks::daily_file_sink_mt>("logfile", 23, 59);
        // default thread pool settings can be modified *before* creating the async logger:
        spdlog::init_thread_pool(10000, 1);  // queue with 10K items and 1 backing thread.
        auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", _my_logger_te_4);

        async_file->set_level(spdlog::level::trace);
        async_file->info("Async message #{}", 1);
        async_file->debug("Async message #{}", 2);
        async_file->trace("Async message #{}", 3);
        async_file->warn("Async message #{}", 4);
        async_file->error("Async message #{}", 5);
        async_file->critical("Async message #{}", 6);

        spdlog::drop_all();
    }
    catch (const spdlog::spdlog_ex& ex)
    {
        std::cout << "Log initialization failed: " << ex.what() << std::endl;
    }
}

//======================== Create multiple loggers that share the same file (sink) aka categories
void te_5()
{
    try
    {
        auto daily_sink = std::make_shared<spdlog::sinks::daily_file_sink_mt>(_my_logger_te_5, 23, 59);
        // create synchronous  loggers
        auto net_logger = std::make_shared<spdlog::logger>("net", daily_sink);
        auto hw_logger = std::make_shared<spdlog::logger>("hw", daily_sink);
        auto db_logger = std::make_shared<spdlog::logger>("db", daily_sink);

        net_logger->set_level(spdlog::level::critical);  // independent levels
        hw_logger->set_level(spdlog::level::debug);

        // globally register the loggers so they can be accessed using spdlog::get(logger_name)
        spdlog::register_logger(net_logger);

        // use the loggers
        spdlog::get("net")->critical("Net critical message");
        hw_logger->debug("HW debug message");
        db_logger->info("DB info message");
    }
    catch (const spdlog::spdlog_ex& ex)
    {
        std::cout << "Log initialization failed: " << ex.what() << std::endl;
    }
}

//==================================== Logger with console and file output.
// the console will show only warnings or worse, while the file will log all messages.
void te_6()
{
    try
    {
        auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
        console_sink->set_level(spdlog::level::warn);
        console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v");

        auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(_my_logger_te_6, true);
        file_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v");
        file_sink->set_level(spdlog::level::trace);

        spdlog::sinks_init_list sink_list = {file_sink, console_sink};

        spdlog::logger logger("multi_sink", sink_list.begin(), sink_list.end());
        logger.set_level(spdlog::level::debug);

        // 使用 logger 输出不同级别的日志
        logger.warn("this should appear in both console and file");
        logger.info("this message should not appear in the console, only in the file");
        logger.trace("This is a trace message");
        logger.debug("This is a debug message");
        logger.error("This is an error message");
        logger.critical("This is a critical message");

        // or you can even set multi_sink logger as default logger
        spdlog::set_default_logger(std::make_shared<spdlog::logger>("multi_sink", spdlog::sinks_init_list({console_sink, file_sink})));
        spdlog::info("use default logger");
    }
    catch (const spdlog::spdlog_ex& ex)
    {
        std::cout << "Log initialization failed: " << ex.what() << std::endl;
    }
}

//============================= 自定义对象输出
class some_class
{
public:
    some_class() = default;
    ~some_class() = default;
    some_class(int code) : code(code) {}

public:
    int code;
};

std::ostream& operator<<(std::ostream& os, const some_class& c) { return os << "some_class"; }

// fmt v10 and above requires `fmt::formatter<T>` extends `fmt::ostream_formatter`.
// See: https://github.com/fmtlib/fmt/issues/3318
template <>
struct fmt::formatter<some_class> : fmt::ostream_formatter
{
};

void te_7()
{
    some_class c(114514);
    auto console = spdlog::stdout_logger_mt("console_7");
    console->info("custom class with operator<<, {}", c);
}

//================================= 使用 format 自定义
class some_class2
{
public:
    some_class2() = default;
    ~some_class2() = default;
    some_class2(int code) : code(code) {}

public:
    int code;

private:
    template <typename OStream>
    friend OStream& operator<<(OStream& os, const some_class2& to_log)
    {
        fmt::format_to(std::ostream_iterator<char>(os), "{:04X}", to_log.code);
        return os;
    }
};

template <>
struct fmt::formatter<some_class2> : fmt::ostream_formatter
{
};

inline void te_8()
{
    some_class2 c(0);
    c.code = 17;
    auto console = spdlog::stdout_logger_mt("console_8");  // 名称不能重复
    console->info("custom class with operator<<, using fmt: {}", c);
}

//=====================
int main()
{
    te_1();
    te_2();
    te_3();
    te_4();
    te_5();
    te_6();
    te_7();

    te_8();

    return 0;
}